package sf.database.jdbc.sql;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sf.common.exception.SmallOrmException;
import sf.core.DBField;
import sf.core.DBObject;
import sf.database.dao.DBContext;
import sf.database.dbinfo.Feature;
import sf.database.dialect.DBDialect;
import sf.database.meta.ColumnMapping;
import sf.database.meta.MetaHolder;
import sf.database.meta.TableMapping;
import sf.database.support.DBMS;
import sf.database.util.OrmUtils;
import sf.database.util.OrmValueUtils;
import sf.database.util.SQLUtils;
import sf.dsl.example.Example;
import sf.dsl.example.ExampleSQL;
import sf.dsl.example.RawValue;
import sf.dsl.example.SelectOpt;
import sf.spring.util.Assert;
import sf.spring.util.CollectionUtils;
import sf.tools.JavaTypeUtils;
import sf.tools.NumberUtils;
import sf.tools.StringUtils;

import javax.persistence.GenerationType;
import java.sql.Connection;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 模型操作sql
 */
public class ModelSql {
    private static final Logger logger = LoggerFactory.getLogger(ModelSql.class);

    /**
     * @param context
     * @param dialect
     * @param query
     * @param forUpdate
     * @param count
     * @param <T>
     * @return
     */
    public static <T extends DBObject> SQLContext getQuerySelect(DBContext context, DBDialect dialect, T query, boolean forUpdate, boolean count) {
        return getQuerySelect(context, dialect, query, forUpdate, count ? SelectOpt.count : null, true);//默认使用example中的分页参数
    }

    /**
     * @param context
     * @param dialect
     * @param query
     * @param forUpdate   是否需要加入 for update
     * @param opt         查询操作
     * @param offsetLimit 是否使用Example中的offsetLimit
     * @param <T>
     * @return
     */
    public static <T extends DBObject> SQLContext getQuerySelect(DBContext context, DBDialect dialect, T query, boolean forUpdate, SelectOpt opt, boolean offsetLimit) {
        SQLContext ctx = new SQLContext();
        TableMapping tm = MetaHolder.getMeta(query.getClass());
        if (query.hasQuery()) {
            Example example = query.useQuery();
            example.setDialect(dialect);
            SQLContext sub = ExampleSQL.getSelectSQLContext(example, OrmUtils.getDynamicTableName(context), opt, offsetLimit);
            if (forUpdate) {
                if (StringUtils.isNotBlank(sub.getSql())) {
                    sub.setSql(sub.getSql() + " for update");
                } else if (StringUtils.isNotBlank(sub.getListSql())) {
                    sub.setListSql(sub.getListSql() + " for update");
                }
            }
            ctx = sub;
        } else {
            StringBuilder sql = new StringBuilder();
            StringBuilder where = new StringBuilder();
            List<SQLParameter> list = new ArrayList<>();
            if (CollectionUtils.isNotEmpty(query.updateValueMap())) {
                for (Map.Entry<DBField, Object> entry : query.updateValueMap().entrySet()) {
                    ColumnMapping cm = tm.getSchemaMap().get(entry.getKey());
                    list.add(new SQLParameter(entry.getValue(), cm));
                    where.append(where.length() > 0 ? " and " : "").append(dialect.wrapKeyword(cm.getRawColumnName())).append(" = ").append(SQLUtils.placeholder());
                }
            }
            sql.append("select ");
            if (opt != null) {
                switch (opt) {
                    case exists:
                        sql.append("1");
                        break;
                    case count:
                        sql.append("count(*)");
                        break;
                    default:
                        sql.append('*');
                        break;
                }
            } else {
                sql.append('*');
            }
            String dynamicTableName = dialect.wrapKeyword(OrmUtils.getDynamicTableName(context, tm));
            sql.append(" from ").append(dynamicTableName);
            if (where.length() > 0) {
                sql.append(" where ").append(where);
            }
            if (forUpdate) {
                sql.append(" for update");
            }
            ctx.setSql(sql.toString());
            if (opt == SelectOpt.page) {
                StringBuilder countSql = new StringBuilder();
                countSql.append(" select count(*) from ").append(dynamicTableName);
                if (where.length() > 0) {
                    countSql.append(" where ").append(where);
                }
                ctx.setCountSql(countSql.toString());
                ctx.setListSql(ctx.getSql());
                ctx.setSql(null);
            }
            ctx.setParas(list);
        }

//        if (query.hasQuery() && query.useQuery().getOffsetLimit() != null) {
//            Example example = query.useQuery();
//            String newSql = dialect.sqlPageList(sql.toString(), example.getOffsetLimit().getOffset(), example.getOffsetLimit().getLimit());
//            sql.delete(0, sql.length());
//            sql.append(newSql);
//        }
        query.clearUpdate();
        return ctx;
    }

    /**
     * 生成插入语句
     * @param context           上下文
     * @param table             映射信息
     * @param dialect           方言
     * @param obj               对象
     * @param useOptimisticLock 是否使用乐观锁
     * @return
     */
    public static SQLContext model2Save(Connection conn, DBContext context, TableMapping table, DBDialect dialect, DBObject obj, boolean useOptimisticLock) {
        SQLContext c = new SQLContext(table);
        StringBuilder sql = new StringBuilder();
        List<SQLParameter> paras = new ArrayList<>();
        sql.append("insert into ").append(dialect.wrapKeyword(OrmUtils.getDynamicTableName(context, table))).append("(");
        List<String> cStr = new ArrayList<>();// 字段列表
        List<String> vStr = new ArrayList<>();// 值列表
        Map<DBField, Object> valueMap = obj.updateValueMap();
        //检查是否需要插入.
        if (valueMap.isEmpty()) {
            logger.warn("没有需要插入的值");
            return c;
        }
        for (ColumnMapping cm : table.getSchemas()) {
            //已存在该值,则直接跳过.
            if (valueMap.containsKey(cm.getField())) {
                continue;
            }
            //sequence支持
            if (cm.getGv() != null && cm.getGv().strategy() == GenerationType.SEQUENCE && StringUtils.isNotBlank(cm.getGv().generator())) {
                if (!dialect.has(Feature.SUPPORT_SEQUENCE)) {
                    throw new RuntimeException("database does not support sequence");
                }
                cStr.add(dialect.wrapKeyword(cm.getRawColumnName()));
                vStr.add(dialect.getSeqNextValSql(cm.getGv().generator()));
            } else if (cm.getUniqueKeyGenerator() != null
                    || (cm.getGv() != null && cm.getGv().strategy() == GenerationType.AUTO && cm.getClz() == String.class)) {
                //处理自定义主键的场景和对于string设置为uuid32类型
//                valueMap.put(cm.getField(), null);//使用此方案,会导致插入主键列在SQL语句中间
                cStr.add(dialect.wrapKeyword(cm.getRawColumnName()));
                vStr.add(SQLUtils.placeholder());

                //处理自定义主键
                Object value = null;
                IdentifierGenerator ig = cm.getIdentifierGenerator();
                if (ig != null) {
                    value = ig.generate(conn, cm);
                } else if (cm.getGv() != null && cm.getGv().strategy() == GenerationType.AUTO && cm.getClz() == String.class) {
                    value = StringUtils.uuid32();
                }
                if (value != null) {
                    OrmValueUtils.setValue(obj, cm, value);
                    valueMap.remove(cm.getField());
                }
                paras.add(new SQLParameter(value, cm));

            } else if (cm.getSaveDefaultValue() != null || (!cm.isPk() && cm.getColumn() != null && !cm.getColumn().unique() && !cm.getColumn().nullable())) {
                //字段默认值处理 SavedDefaultValue dv = cm.getSavedDefaultValue();
                //尽力了
                Object value = OrmValueUtils.getValue(obj, cm);
                valueMap.put(cm.getField(), value);
            } else if (cm.isCreatedDate() || cm.isLastModifiedDate()) {
                //创建时间,修改时间
                dealPreDate(obj, valueMap, cm);
            }
        }
        //处理值
        for (Map.Entry<DBField, Object> e : valueMap.entrySet()) {
            DBField colName = e.getKey();
            ColumnMapping column = table.getSchemaMap().get(colName);
            if (column != null && !column.isNotInsert()) {
                cStr.add(dialect.wrapKeyword(column.getRawColumnName()));
                Object value = e.getValue();
                if (value instanceof RawValue) {
                    vStr.add(((RawValue) value).getContext());
                } else {
                    vStr.add(SQLUtils.placeholder());
                    paras.add(new SQLParameter(value, column));
                }
            }
        }
        if (useOptimisticLock) {
            //乐观锁插入处理
            optimisticLockInsert(context, table, obj, dialect, paras, cStr, vStr);
        }
        for (SQLParameter p : paras) {
            Object value = p.getValue();
            if (value == null && p.getColumnMapping() != null) {
                value = OrmValueUtils.getValue(obj, p.getColumnMapping());
            }
            p.setValue(value);
        }
        sql.append(StringUtils.join(cStr, ",")).append(") values(").append(StringUtils.join(vStr, ",")).append(")");
        c.setSql(sql.toString());
        c.setParas(paras);
        if (paras.isEmpty()) {
            throw new SmallOrmException("no values need to be saved");
        }
        return c;
    }

    /**
     * 乐观锁插入处理
     * @param obj
     * @param paras
     * @param cStr
     * @param vStr
     */
    private static void optimisticLockInsert(DBContext context, TableMapping table, DBObject obj, DBDialect dialect, List<SQLParameter> paras, List<String> cStr, List<String> vStr) {
        if (!table.getVersionMap().isEmpty()) {
            for (Map.Entry<DBField, ColumnMapping> e : table.getVersionMap().entrySet()) {
                ColumnMapping cm = e.getValue();
                String columnName = cm.getRawColumnName();
                Object value = obj.updateValueMap().get(e.getKey());
                if (value == null) {
                    if (JavaTypeUtils.isNumberClass(cm.getClz())) {
                        //数字,则版本加1
                        value = 0;
                    } else if (cm.getClz() == String.class) {
                        //字符串,则使用UUID
                        value = StringUtils.uuid32();
                    } else if (Date.class.isAssignableFrom(cm.getClz())) {
                        //日期 则使用当前日期
                        value = new Date();
                    } else {
                        throw new UnsupportedOperationException("Optimistic lock:" + e.getKey().getClass() + " incorrect settings");
                    }
                    //回写值
                    OrmValueUtils.setValue(obj, cm, value);
                    //设置参数
                    cStr.add(dialect.wrapKeyword(columnName));
                    vStr.add(SQLUtils.placeholder());
                    paras.add(new SQLParameter(value, cm));
                }
            }
        }
    }

    /**
     * 更新对象,如果有乐观锁,会根据乐观锁条件更新,并会更新数据库中的乐观锁条件.但不会更新对象的乐观锁字段的值.
     * @param context
     * @param table
     * @param dialect
     * @param obj
     * @param c
     * @param useOptimisticLock 是否使用乐观锁
     * @param useNvl            是否使用mysql ifnull,oralce nvl,sqlserver isnull,postgresql coalesce
     * @param preUpdate         是否提前处理数据
     * @return
     */
    public static SQLContext forModelUpdate(DBContext context, TableMapping table, DBDialect dialect, DBObject obj, /*not null*/SQLContext c,
                                            boolean useOptimisticLock, boolean useNvl) {
//        if (c == null) {
//            c = new SQLContext(table);
//        } else {
        c.setTableMapping(table);
//        }
        StringBuilder sql = new StringBuilder();
        StringBuilder setSql = new StringBuilder();
        List<SQLParameter> setParas = new ArrayList<>();
        StringBuilder whereSql = new StringBuilder();
        List<SQLParameter> whereParas = new ArrayList<>();
        List<ColumnMapping> pKeys = table.getPkFields();
        Map<DBField, Object> updateValueMap = obj.updateValueMap();
        //检查是否需要更新.
        if (updateValueMap.isEmpty()) {
            logger.warn("没有需要更新的值");
            return c;
        }
        boolean hasQuery = obj.hasQuery();
        if (useOptimisticLock) {
            //检查乐观锁前置条件
            if (!hasQuery && !table.getVersionMap().isEmpty()) {
                for (Map.Entry<DBField, ColumnMapping> e : table.getVersionMap().entrySet()) {
                    if (!updateValueMap.containsKey(e.getKey())) {
                        throw new SmallOrmException("Optimistic lock not set:" + e.getKey().getClass());
                    }
                }
            }
            //乐观锁set处理
            //FIXME 未考虑返回值的情况.考虑到异常情况下的重试,对更新方法不设置对象为更新后的乐观锁比较好.此处当异常时,可以重试.
            optimisticLockUpdate(table, obj, dialect, c, setSql, setParas, whereSql, whereParas);
        }
        //数据修正
        modelPreUpdate(table, obj);

        //设置值
        for (Map.Entry<DBField, Object> e : updateValueMap.entrySet()) {
            DBField dbField = e.getKey();
            ColumnMapping cm = table.getSchemaMap().get(dbField);
            //判断是否是主键
            boolean isPk = cm.isPk();

            //判断是否是乐观锁,如果是,则忽略.
            boolean version = useOptimisticLock && cm.isVersion();
            //必须包含该字段,且不为主键,不是乐观锁字段.
            if (!cm.isNotUpdate() && (hasQuery || !isPk) && !version) {
                setSql.append(setSql.length() > 0 ? "," : "").append(dialect.wrapKeyword(cm.getRawColumnName())).append("=");
                Object value = e.getValue();
                if (value instanceof RawValue) {//直接保存原始值
                    setSql.append(((RawValue) value).getContext());
                } else {
                    setSql.append(useNvl ? dialect.getNvlFunction(SQLUtils.placeholder(), dialect.wrapKeyword(cm.getRawColumnName())) : SQLUtils.placeholder());
                    setParas.add(new SQLParameter(e.getValue(), cm));
                }
            }
        }
        //动态表名处理
        String tableName = null;
        //条件处理
        if (hasQuery) {
            Example example = obj.useQuery();
            example.setDialect(dialect);
            ExampleSQL.setDynamicTableName(example, OrmUtils.getDynamicTableName(context));
            tableName = ExampleSQL.getFromTableName(example);
            ExampleSQL.getSQLContextWhereClause(whereSql, whereParas, example);
        } else {
            tableName = dialect.wrapKeyword(OrmUtils.getDynamicTableName(context, table));
            for (ColumnMapping pk : pKeys) {
                whereSql.append(whereSql.length() > 0 ? " and " : "").append(dialect.wrapKeyword(pk.getRawColumnName())).append('=').append(SQLUtils.placeholder());
                //取值
                Object o = updateValueMap.get(pk.getField());
                if (o == null) {
                    o = OrmValueUtils.getValue(obj, pk);
                }
                if (o == null) {
//                    LogUtil.warn("更新缺少主键条件!");
                    throw new SmallOrmException("Update missing primary key condition!");
//                    return c;
                }
                whereParas.add(new SQLParameter(o, pk));
            }
        }
        //检查是否需要更新.
        if (setSql.length() == 0) {
            throw new SmallOrmException("no value to be updated");
        }
        //拼装SQL
        if (DBMS.clickhouse.getNumber() == dialect.getNumber()) {//clickhouse支持
            sql.append("ALTER TABLE ").append(tableName).append(" update ");
        } else {
            sql.append("update ").append(tableName).append(" set ");
        }
        sql.append(setSql);
        if (whereSql.length() > 0) {
            sql.append(" where ").append(whereSql);
        }
        c.setSql(sql.toString());
        setParas.addAll(whereParas);
        c.setParas(setParas);
        return c;
    }

    private static void optimisticLockUpdate(TableMapping table, DBObject obj, DBDialect dialect, SQLContext c,
                                             StringBuilder setSql, List<SQLParameter> setParas, StringBuilder whereSql, List<SQLParameter> whereParas) {
        if (!table.getVersionMap().isEmpty()) {
            //记录变更后的值
            List<SQLParameter> preResultPara = new ArrayList<>();
            for (Map.Entry<DBField, ColumnMapping> e : table.getVersionMap().entrySet()) {
                ColumnMapping cm = e.getValue();
                String wrapColumnName = dialect.wrapKeyword(cm.getRawColumnName());
                Object value = obj.updateValueMap().get(e.getKey());
                if (JavaTypeUtils.isNumberClass(cm.getClz())) {
                    //数字,则版本加1
                    setSql.append(setSql.length() > 0 ? "," : "").append(wrapColumnName).append("=").append(wrapColumnName).append("+1");
                    Long preValue = ((Number) value).longValue() + 1;
                    SQLParameter op = new SQLParameter(NumberUtils.getTargetNumber(preValue, cm.getClz()), cm);
                    preResultPara.add(op);
                } else if (cm.getClz() == String.class) {
                    //字符串,则使用UUID
                    String uuid = StringUtils.uuid32();
                    setSql.append(setSql.length() > 0 ? "," : "").append(wrapColumnName).append("='").append(uuid).append('\'');
                    SQLParameter op = new SQLParameter(uuid, cm);
                    preResultPara.add(op);
                } else if (Date.class.isAssignableFrom(cm.getClz())) {
                    //日期 则使用当前日期
                    setSql.append(setSql.length() > 0 ? "," : "").append(wrapColumnName).append('=').append(SQLUtils.placeholder());
                    SQLParameter op = new SQLParameter(new Date(), cm);
                    setParas.add(op);
                    preResultPara.add(op);
                }
                //乐观锁设置where条件
                whereSql.append(whereSql.length() > 0 ? " and " : "").append(wrapColumnName).append(" = ").append(SQLUtils.placeholder());
                whereParas.add(new SQLParameter(value, cm));
            }
            //设置处理后的返回值
            c.setPreResultParas(preResultPara);
        }
    }

    /**
     * 数据修正,主要使用于批量场景
     * @param table
     * @param obj
     */
    public static void modelPreSave(TableMapping table, DBObject obj) {
        Map<DBField, Object> valueMap = obj.updateValueMap();
        for (ColumnMapping cm : table.getSpecialFields()) {
            //已存在该值,则直接跳过.
            if (valueMap.containsKey(cm.getField())) {
                continue;
            }
            if (cm.isCreatedDate() || cm.isLastModifiedDate()) {
                dealPreDate(obj, valueMap, cm);
            }
        }
    }

    /**
     * 数据修正,主要使用于批量场景
     * @param table
     * @param obj
     */
    public static void modelPreUpdate(TableMapping table, DBObject obj) {
        Map<DBField, Object> valueMap = obj.updateValueMap();
        for (ColumnMapping cm : table.getSpecialFields()) {
            //已存在该值,则直接跳过.
            if (valueMap.containsKey(cm.getField())) {
                continue;
            }
            if (cm.isLastModifiedDate()) {
                //修改时间
                dealPreDate(obj, valueMap, cm);
            }
        }
    }


    private static void dealPreDate(DBObject obj, Map<DBField, Object> valueMap, ColumnMapping cm) {
        //创建时间,修改时间
        Object nowTime = getNowTime(cm.getClz());
        if (nowTime != null) {
            valueMap.put(cm.getField(), nowTime);
            OrmValueUtils.setValue(obj, cm, nowTime);
        }
    }

    /**
     * 根据类型返回当前时间
     * @param clz
     * @return
     */
    private static Object getNowTime(Class<?> clz) {
        if (Date.class.isAssignableFrom(clz)) {
            return new Date();
        } else if (Long.class.isAssignableFrom(clz) || Long.TYPE.isAssignableFrom(clz)) {
            return new Date().getTime();
        } else if (Instant.class.isAssignableFrom(clz)) {
            return Instant.now();
        } else if (LocalDate.class.isAssignableFrom(clz)) {
            return LocalDate.now();
        } else if (LocalDateTime.class.isAssignableFrom(clz)) {
            return LocalDateTime.now();
        } else if (LocalTime.class.isAssignableFrom(clz)) {
            return LocalTime.now();
        } else if (OffsetDateTime.class.isAssignableFrom(clz)) {
            return OffsetDateTime.now();
        } else if (ZonedDateTime.class.isAssignableFrom(clz)) {
            return ZonedDateTime.now();
        } else if (GregorianCalendar.class.isAssignableFrom(clz)) {
            return GregorianCalendar.getInstance();
        } else if (Calendar.class.isAssignableFrom(clz)) {
            return Calendar.getInstance();
        }
        return null;
    }

    public static SQLContext forModelDeleteByIds(DBContext context, TableMapping table, DBDialect dialect, Object... keyParams) {
        SQLContext c = new SQLContext(table);
        List<SQLParameter> paras = new ArrayList<>();
        List<ColumnMapping> pKeys = table.getPkFields();
        StringBuilder sql = new StringBuilder("delete from ").append(dialect.wrapKeyword(OrmUtils.getDynamicTableName(context, table)));
        Assert.notEmpty(pKeys, "主键为空!");
        Assert.isTrue(keyParams != null, "主键参数为空!");
        Assert.isTrue(pKeys.size() == keyParams.length, "主键和传入的参数个数不一致!");
        for (int i = 0; i < pKeys.size(); i++) {
            sql.append(i > 0 ? " and " : " where ").append(dialect.wrapKeyword(pKeys.get(i).getRawColumnName())).append(" = ").append(SQLUtils.placeholder());
            Object o = keyParams[i];
            paras.add(new SQLParameter(o, pKeys.get(i)));
        }
        c.setSql(sql.toString());
        c.setParas(paras);
        return c;
    }

    public static SQLContext forModelDelete(DBContext context, TableMapping table, DBDialect dialect, DBObject obj) {
        SQLContext c = new SQLContext(table);
        StringBuilder sql = new StringBuilder();
        List<SQLParameter> paras = new ArrayList<>();
        List<ColumnMapping> pKeys = table.getPkFields();
        String tableName = null;
        StringBuilder where = new StringBuilder();
        Map<DBField, Object> valueMap = obj.updateValueMap();
        boolean existPK = true;//是否存在主键
        for (ColumnMapping cm : pKeys) {
            if (!valueMap.containsKey(cm.getField())) {
                Object o = OrmValueUtils.getValue(obj, cm);
                if (o == null || (cm.getClz().isPrimitive()
                        && Objects.equals(o, JavaTypeUtils.getClassPrimitiveDefaultValue(cm.getClz()))/*且等于默认值*/)) {
                    existPK = false;
                }
            }
        }
        if (obj.hasQuery()) {
            Example example = obj.useQuery();
            example.setDialect(dialect);
            ExampleSQL.setDynamicTableName(example, OrmUtils.getDynamicTableName(context));
            tableName = ExampleSQL.getFromTableName(example);
            ExampleSQL.getSQLContextWhereClause(where, paras, example);
        } else {
            tableName = dialect.wrapKeyword(OrmUtils.getDynamicTableName(context, table));
            if (existPK || valueMap.isEmpty()) {
                for (ColumnMapping cm : pKeys) {
                    Object o = OrmValueUtils.getValue(obj, cm);
                    if (o != null) {
                        where.append(where.length() > 0 ? " and " : "").append(dialect.wrapKeyword(cm.getRawColumnName())).append(" = ").append(SQLUtils.placeholder());
                        paras.add(new SQLParameter(o, cm));
                    } else {
                        throw new SmallOrmException("The primary key is empty and cannot be deleted!");
                    }
                }
            } else {
                for (Map.Entry<DBField, Object> entry : valueMap.entrySet()) {
                    where.append(where.length() > 0 ? " and " : "").append(dialect.wrapKeyword(table.getSchemaMap().get(entry.getKey()).getRawColumnName())).append(" = ");
                    Object o = entry.getValue();
                    if (o instanceof RawValue) {
                        where.append(((RawValue) o).getContext());
                    } else {
                        where.append(SQLUtils.placeholder());
                        paras.add(new SQLParameter(o, table.getSchemaMap().get(entry.getKey())));
                    }
                }
            }
        }
        if (DBMS.clickhouse.getNumber() == dialect.getNumber()) {//clickhouse支持
            sql.append("ALTER TABLE ").append(tableName).append(" delete ");
        } else {
            sql.append("delete from ").append(tableName);
        }
        if (where.length() > 0) {
            sql.append(" where ").append(where);
        }
        c.setSql(sql.toString());
        c.setParas(paras);
        return c;
    }

    public static SQLContext forModelSelectByIds(DBContext context, TableMapping table, DBDialect dialect, Object... keyParams) {
        SQLContext c = new SQLContext(table);
        List<ColumnMapping> pKeys = table.getPkFields();
        List<SQLParameter> parameters = new ArrayList<>();
        StringBuilder sql = new StringBuilder();
        sql.append("select * from ").append(dialect.wrapKeyword(OrmUtils.getDynamicTableName(context, table)));
        for (int i = 0; i < pKeys.size(); i++) {
            ColumnMapping pk = pKeys.get(i);
            sql.append(i > 0 ? " and " : " where ").append(dialect.wrapKeyword(pk.getRawColumnName())).append(" =").append(SQLUtils.placeholder());
            parameters.add(new SQLParameter(keyParams[i], pk));
        }
        c.setSql(sql.toString());
        c.setParas(parameters);
        return c;
    }

    public static SQLContext forModelExistsByIds(DBContext context, TableMapping table, DBDialect dialect, Object... keyParams) {
        SQLContext c = new SQLContext(table);
        List<ColumnMapping> pKeys = table.getPkFields();
        List<SQLParameter> parameters = new ArrayList<>();
        StringBuilder sql = new StringBuilder();
        sql.append("select 1 from ").append(dialect.wrapKeyword(OrmUtils.getDynamicTableName(context, table)));
        for (int i = 0; i < pKeys.size(); i++) {
            ColumnMapping pk = pKeys.get(i);
            sql.append(i > 0 ? " and " : " where ").append(dialect.wrapKeyword(pk.getRawColumnName())).append(" =").append(SQLUtils.placeholder());
            parameters.add(new SQLParameter(keyParams[i], pk));
        }
        c.setSql(dialect.sqlPageList(sql, 0, 1).toString());
        c.setParas(parameters);
        return c;
    }

    public static <T extends DBObject> String getSelectCountSql(DBDialect dialect, Class<T> clz) {
        StringBuilder sql = new StringBuilder();
        TableMapping tm = MetaHolder.getMeta(clz);
        sql.append("select count(*) from ").append(dialect.wrapKeyword(tm.getTableName()));
        return sql.toString();
    }
}
